package com.danicsoft.daide.command;

import java.util.List;

import com.danicsoft.daide.token.StandardToken;
import com.danicsoft.daide.token.Token;
import com.danicsoft.daide.token.TokenMap;
import com.danicsoft.daide.token.TokenMap.Orders;
import com.danicsoft.daide.token.TokenGroup;

/**
 * An <code>OrderCommand</code> is responsible for representing an order. An
 * order is thought of as consiting of three parts: the subject, the predicate
 * and optionally, the target. For example, a move command specifies a subject
 * (a unit), a predicate (MTO) and a target (another province).
 * 
 * @author Daniel Yule (daniel.yule@gmail.com)
 * @version 1.0
 */
public class OrderSpec implements IOTarget {

	/**
	 * A target for a build order. Doesn't actually have a target, is just here
	 * for consistancy's sake
	 * 
	 * @author Daniel Yule (daniel.yule@gmail.com)
	 * @version 1.0
	 */
	public static class BuildTarget implements OrderTarget {

		public void doIO(TokenIO tokenIO, TokenGroup tokens) throws ParseException {

		}

		public byte getType() {
			return Orders.BLD;
		}

		public IOTarget newInstance() {
			return new BuildTarget();
		}

	}

	/**
	 * A target for a convoy move to command. It has a destination province, and
	 * a list of provinces that the move goes through.
	 * 
	 * @author Daniel Yule (daniel.yule@gmail.com)
	 * @version 1.0
	 */
	public static class ConvoyMoveTarget implements OrderTarget {

		/**
		 * The province we are moving to.
		 */
		public StandardToken dstProv;

		/**
		 * The sea provinces we'll be moving through to get to dstProv.
		 */
		public List<Token> viaProvinces;

		/**
		 * Default constructor for use in IO.
		 */
		private ConvoyMoveTarget() {}

		/**
		 * Create a new target that specifies a convoy move through the
		 * provinces listed, ending at the given province.
		 * 
		 * @param viaProvinces
		 *        The provinces that will convoy this move.
		 * @param dstProvince
		 *        The destination province.
		 */
		private ConvoyMoveTarget(List<Token> viaProvinces, StandardToken dstProvince) {
			this.viaProvinces = viaProvinces;
			dstProv = dstProvince;
		}

		public void doIO(TokenIO tokenIO, TokenGroup tokens) throws ParseException {
			dstProv = (StandardToken) tokenIO.doToken(tokens, dstProv, TokenMap.provinces);
			if (!tokenIO.doConditional(tokens, TokenMap.getOrderToken(Orders.VIA), true)) {
				throw new ParseException("Expected a VIA token");
			}
			viaProvinces = tokenIO.doTokenList(tokens, viaProvinces, TokenMap.provinces);

		}

		public byte getType() {
			return Orders.CTO;
		}

		public IOTarget newInstance() {
			return new ConvoyMoveTarget();
		}

	}

	/**
	 * A target for Convoy orders. A Convoy order specifies a starting place and
	 * an ending province.
	 * 
	 * @author Daniel Yule (daniel.yule@gmail.com)
	 * @version 1.0
	 */
	public static class ConvoyTarget implements OrderTarget {

		/**
		 * The unit we are convoying.
		 */
		public UnitPos ConvoyUnit;

		/**
		 * The province we are convoying to.
		 */
		public Token convoyingTo;

		/**
		 * Default constructor for use in IO.
		 */
		private ConvoyTarget() {
			ConvoyUnit = new UnitPos();
		}

		/**
		 * Construct a new target that specifies a convoy from a location to a
		 * particular province.
		 * 
		 * @param convoyUnit
		 *        The source location of the unit we are transporting.
		 * @param convoyingTo
		 *        The destination province.
		 */
		private ConvoyTarget(UnitPos convoyUnit, StandardToken convoyingTo) {
			this.ConvoyUnit = convoyUnit;
			this.convoyingTo = convoyingTo;
		}

		public void doIO(TokenIO tokenIO, TokenGroup tokens) throws ParseException {
			ConvoyUnit.doIO(tokenIO, tokens);
			if (!tokenIO.doConditional(tokens, TokenMap.getOrderToken(Orders.CTO), true)) {
				throw new ParseException("Expected a CTO token");
			}
			convoyingTo = tokenIO.doToken(tokens, convoyingTo, TokenMap.provinces);

		}

		public byte getType() {
			return Orders.CVY;
		}

		public IOTarget newInstance() {
			return new ConvoyTarget();
		}

	}

	/**
	 * A target for a disband order. Doesn't actually have a target, is just
	 * here for consistancy's sake
	 * 
	 * @author Daniel Yule (daniel.yule@gmail.com)
	 * @version 1.0
	 */
	public static class DisbandTarget implements OrderTarget {

		public void doIO(TokenIO tokenIO, TokenGroup tokens) throws ParseException {

		}

		public byte getType() {
			return Orders.DSB;
		}

		public IOTarget newInstance() {
			return new DisbandTarget();
		}

	}

	/**
	 * A target for a hold order. Doesn't actually have a target, is just here
	 * for consistancy's sake
	 * 
	 * @author Daniel Yule (daniel.yule@gmail.com)
	 * @version 1.0
	 */
	public static class HoldTarget implements OrderTarget {

		public void doIO(TokenIO tokenIO, TokenGroup tokens) throws ParseException {

		}

		public byte getType() {
			return Orders.HLD;
		}

		public IOTarget newInstance() {
			return new HoldTarget();
		}

	}

	/**
	 * A target for the Move command. Contains a province that we shall be
	 * moving to.
	 * 
	 * @author Daniel Yule (daniel.yule@gmail.com)
	 * @version 1.0
	 */
	public static class MoveTarget implements OrderTarget {

		/**
		 * The province that this move command is moving to.
		 */
		public Token Province;

		/**
		 * Default constructor for use in IO.
		 */
		private MoveTarget() {}

		/**
		 * Create a new Target that specifies the province to move to.
		 * 
		 * @param province
		 *        The province to move to.
		 */
		private MoveTarget(Token province) {
			Province = province;
		}

		public void doIO(TokenIO tokenIO, TokenGroup tokens) throws ParseException {

			Province = tokenIO.doToken(tokens, Province, TokenMap.provinces);
		}

		public byte getType() {
			return Orders.MTO;
		}

		public IOTarget newInstance() {
			return new MoveTarget();
		}

	}

	/**
	 * The target of this command. It could be very complex (as in the convoy
	 * commands) or simply (as in the move commands) each subclass is
	 * responsible for the details.
	 * 
	 * @author Daniel Yule (daniel.yule@gmail.com)
	 * @version 1.0
	 */
	public interface OrderTarget extends IOTarget {
		/**
		 * Gets the subtype of this OrderTarget (WVE, CTO, etc)
		 * 
		 * @return A byte representing the type of this <code>OrderTarget</code>
		 */
		public byte getType();
	}

	/**
	 * A target for a remove order. Doesn't actually have a target, is just here
	 * for consistancy's sake
	 * 
	 * @author Daniel Yule (daniel.yule@gmail.com)
	 * @version 1.0
	 */
	public static class RemoveTarget implements OrderTarget {

		public void doIO(TokenIO tokenIO, TokenGroup tokens) throws ParseException {

		}

		public byte getType() {
			return Orders.REM;
		}

		public IOTarget newInstance() {
			return new RemoveTarget();
		}

	}

	/**
	 * A target for the Retreat command. Contains a province that we shall be
	 * retreating to.
	 * 
	 * @author Daniel Yule (daniel.yule@gmail.com)
	 * @version 1.0
	 */
	public static class RetreatTarget implements OrderTarget {

		/**
		 * The province that this retreat command is retreating to.
		 */
		public Token Province;

		/**
		 * Default constructor for use in IO.
		 */
		private RetreatTarget() {}

		/**
		 * Create a new Target that specifies the province to retreat to.
		 * 
		 * @param province
		 *        The province to retreat to.
		 */
		private RetreatTarget(Token province) {
			Province = province;
		}

		public void doIO(TokenIO tokenIO, TokenGroup tokens) throws ParseException {

			Province = tokenIO.doToken(tokens, Province, TokenMap.provinces);
		}

		public byte getType() {
			return Orders.RTO;
		}

		public IOTarget newInstance() {
			return new RetreatTarget();
		}

	}

	/**
	 * A target for support orders. A support order can either support a unit to
	 * hold or a unit to move.
	 * 
	 * @author Daniel Yule (daniel.yule@gmail.com)
	 * @version 1.0
	 */
	public static class SupportTarget implements OrderTarget {

		/**
		 * The unit we are supporting, either to hold or move.
		 */
		public UnitPos supportUnit = new UnitPos();

		/**
		 * The province the unit we are supporting is moving to, if there is a
		 * move.
		 */
		public Token movingTo;

		/**
		 * Flags whether we are supporting a move or not.
		 */
		private boolean isForMove;

		/**
		 * Default constrctor for use in IO.
		 */
		private SupportTarget() {
			isForMove = false;
		}

		/**
		 * Construct a new target that specifies support for an unmoving unit.
		 * 
		 * @param supportPos
		 *        The stationary unit to support.
		 */
		private SupportTarget(UnitPos supportPos) {
			this.supportUnit = supportPos;
			isForMove = false;
		}

		/**
		 * Construct a new target that specifies support for a moving unit.
		 * 
		 * @param supportPos
		 *        The unit that is moving.
		 * @param movingTo
		 *        Where it is moving to.
		 */
		private SupportTarget(UnitPos supportPos, Token movingTo) {
			this(supportPos);
			this.movingTo = movingTo;
			isForMove = true;
		}

		public void doIO(TokenIO tokenIO, TokenGroup tokens) throws ParseException {
			supportUnit.doIO(tokenIO, tokens);
			isForMove = tokenIO.doConditional(tokens, TokenMap.getOrderToken(Orders.MTO), isForMove);
			if (isForMove) {
				movingTo = tokenIO.doToken(tokens, movingTo, TokenMap.provinces);
			}
		}

		public byte getType() {
			return Orders.SUP;
		}

		/**
		 * Gets whether we are supporting a move or not
		 * 
		 * @return True if we are supporting a move, false if we are supporting
		 *         an unmoving unit.
		 */
		public boolean isForMove() {
			return isForMove;
		}

		public IOTarget newInstance() {
			return new SupportTarget();
		}

	}

	/**
	 * A target for a waive order. Doesn't actually have a target, is just here
	 * for consistancy's sake
	 * 
	 * @author Daniel Yule (daniel.yule@gmail.com)
	 * @version 1.0
	 */
	public static class WaiveTarget implements OrderTarget {

		public void doIO(TokenIO tokenIO, TokenGroup tokens) throws ParseException {

		}

		public byte getType() {
			return Orders.WVE;
		}

		public IOTarget newInstance() {
			return new WaiveTarget();
		}

	}

	/**
	 * Get an order that specifies to build a new unit.
	 * 
	 * @param position
	 *        The position to build a unit in and the type of unit to build.
	 * @return An <code>OrderSpec</code> specifying a build order.
	 */
	public static OrderSpec getBuildOrder(UnitPos position) {
		return new OrderSpec(position, TokenMap.getOrderToken(Orders.BLD), new BuildTarget());
	}

	/**
	 * Get an order that specifies to convoy move a unit.
	 * 
	 * @param position
	 *        The position where the unit we are convoying is located.
	 * @param viaProvinces
	 *        The provinces to use to convoy along.
	 * @param destinationProvince
	 *        The province we are being convoyed to.
	 * @return An <code>OrderSpec</code> specifying a convoy move order.
	 */
	public static OrderSpec getConvoyMove(UnitPos position, List<Token> viaProvinces, StandardToken destinationProvince) {
		return new OrderSpec(position, TokenMap.getOrderToken(Orders.CTO), new ConvoyMoveTarget(viaProvinces, destinationProvince));
	}

	/**
	 * Get an order that specifies to build a new unit.
	 * 
	 * @param position
	 *        The position to build a unit in and the type of unit to build.
	 * @param sourceProvince
	 *        The unit we are convoying
	 * @param destinationProvince
	 *        The province we are convoying the unit to.
	 * @return An <code>OrderSpec</code> specifying a convoy order.
	 */
	public static OrderSpec getConvoy(UnitPos position, UnitPos sourceProvince, StandardToken destinationProvince) {
		return new OrderSpec(position, TokenMap.getOrderToken(Orders.CVY), new ConvoyTarget(sourceProvince, destinationProvince));
	}

	/**
	 * Get an order that specifies to disband a unit.
	 * 
	 * @param position
	 *        The position of the unit to disband
	 * @return An <code>OrderSpec</code> specifying a disband order.
	 */
	public static OrderSpec getDisbandOrder(UnitPos position) {
		return new OrderSpec(position, TokenMap.getOrderToken(Orders.DSB), new DisbandTarget());
	}

	/**
	 * Get an order that specifies to hold.
	 * 
	 * @param position
	 *        The position to hold.
	 * @return An <code>OrderSpec</code> specifying a hold order.
	 */
	public static OrderSpec getHoldOrder(UnitPos position) {
		return new OrderSpec(position, TokenMap.getOrderToken(Orders.HLD), new HoldTarget());
	}

	/**
	 * Get an order that specifies to remove a unit.
	 * 
	 * @param position
	 *        The position of the unit to remove.
	 * @return An <code>OrderSpec</code> specifying a remove order.
	 */
	public static OrderSpec getRemoveOrder(UnitPos position) {
		return new OrderSpec(position, TokenMap.getOrderToken(Orders.REM), new RemoveTarget());
	}

	/**
	 * Get an order that specifies to waive construction.
	 * 
	 * @param power
	 *        The power to waive construction for.
	 * @return An <code>OrderSpec</code> specifying a waive order.
	 */
	public static OrderSpec getWaiveOrder(StandardToken power) {
		return new OrderSpec(power);
	}

	/**
	 * The action for this order (MTO, CTO, HLD)
	 */
	public StandardToken predicate;

	/**
	 * The subject (Power, type, province) for this order.
	 */
	public UnitPos UnitPos = new UnitPos();

	/**
	 * The target of this order.
	 */
	public OrderTarget target;

	/**
	 * The power we are waiving a build for.
	 */
	public StandardToken power;

	/**
	 * Specifies whether or not this is a waive based order.
	 */
	private boolean isWaive;

	/**
	 * Create a new OrderSpec for use with IO
	 */
	OrderSpec() {}

	/**
	 * Create a new OrderSpec that specifies a waive for the given power.
	 * 
	 * @param power
	 *        The power to specify a waive for.
	 */
	private OrderSpec(StandardToken power) {
		this.power = power;
		this.predicate = TokenMap.getOrderToken(Orders.WVE);
		this.target = new WaiveTarget();
	}

	/**
	 * Create a new OrderSpec and specify it's initial arguments.
	 * 
	 * @param position
	 * @param predicate
	 * @param target
	 */
	private OrderSpec(UnitPos position, StandardToken predicate, OrderTarget target) {
		UnitPos retreatLessPos = new UnitPos();
		retreatLessPos.power = position.power;
		retreatLessPos.isRetreating = false;
		retreatLessPos.province = position.province;
		retreatLessPos.unit_type = position.unit_type;
		this.UnitPos = retreatLessPos;
		this.predicate = predicate;
		this.target = target;
	}

	public void doIO(TokenIO tokenIO, TokenGroup tokens) throws ParseException {
		TokenGroup sGroup = tokenIO.subGroup(tokens);
		if (tokenIO.doIfGroup(sGroup, !isWaive)) {

			UnitPos.doIO(tokenIO, sGroup);
			// TODO make this work with WVE, which will crash it currently
			predicate = (StandardToken) tokenIO.doToken(sGroup, predicate, TokenMap.orders);
			if (target == null) {
				target = getNewTarget();
			}
			target.doIO(tokenIO, sGroup);
			isWaive = false;

		}
		else {
			power = (StandardToken) tokenIO.doToken(sGroup, power, TokenMap.provinces);
			predicate = (StandardToken) tokenIO.doToken(sGroup, predicate, TokenMap.orders);
			if (target == null) {
				target = new WaiveTarget();
			}
		}

	}

	@Override
	public boolean equals(Object obj) {
		return obj == null ? false : obj.hashCode() == hashCode();
	}

	@Override
	public int hashCode() {
		return UnitPos.hashCode();
	}

	public IOTarget newInstance() {
		return new OrderSpec();
	}

	@Override
	public String toString() {
		return "Order";
		// TODO make this all pretty and such.
	}

	/**
	 * Gets a new <code>OrderTarget</code> based on the predicate of the
	 * order.
	 * 
	 * @return An <code>OrderTarget</code> corrresponding to the type of the
	 *         predicate.
	 * @throws ParseException
	 *         If an unknown type was passed to the predicate.
	 */
	private OrderTarget getNewTarget() throws ParseException {
		switch (predicate.getData()) {
			case Orders.BLD:
				return new BuildTarget();
			case Orders.CTO:
				return new ConvoyMoveTarget();
			case Orders.CVY:
				return new ConvoyTarget();
			case Orders.DSB:
				return new DisbandTarget();
			case Orders.HLD:
				return new HoldTarget();
			case Orders.MTO:
				return new MoveTarget();
			case Orders.REM:
				return new RemoveTarget();
			case Orders.RTO:
				return new RetreatTarget();
			case Orders.SUP:
				return new SupportTarget();
			case Orders.WVE:
				return new WaiveTarget();
		}
		throw new ParseException("Encountered unexpected order predicate");
	}

}
